﻿#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using Beaker;
using Beaker.OpenCube.Game;
using Beaker.OpenCube.Game.Blocks;
using Beaker.OpenCube.Textures;

#endregion

namespace Beaker.OpenCube.Drawing
{
	public sealed class ChunkPainter
	{
		private readonly ITexturePack _texturePack;
		private readonly Dictionary<Block, Color> _blockColorMapping;

		/// <summary>
		/// Initializes a new instance of the <see cref="ChunkPainter"/> class.
		/// </summary>
		/// <param name="texturePack">The texture pack to use for chunk painting.</param>
		public ChunkPainter( ITexturePack texturePack )
		{
			Contract.Requires<ArgumentNullException>( texturePack != null );
			_texturePack = texturePack;

			// for chunk painting, we need a block colour mapping dictionary to quicly render single pixel per block
			_blockColorMapping = new Dictionary<Block, Color>();
			foreach( Block block in Block.SubBlockMapping.SelectMany( bl => bl.Value ) )
				AddColourFor( block );
		}

		private void AddColourFor( Block block )
		{
			// special casings for blocks that should take from colourizers
			if( block == Block.Grass )
			{
				Color color = _texturePack.GetBiomeColour( BiomeColorType.Default, BiomeColorBlockType.Grass );
				_blockColorMapping.Add( block, color );
			}
			else if( block.Type == BlockType.Leaves )
			{
				Color color = _texturePack.GetBiomeColour( BiomeColorType.Default, BiomeColorBlockType.Foliage );
				_blockColorMapping.Add( block, color );
			}
			else if( block.Type == BlockType.Water || block.Type == BlockType.StationaryWater )
			{
				Color color = _texturePack.GetBiomeColour( BiomeColorType.Default, BiomeColorBlockType.Water );
				_blockColorMapping.Add( block, color );
			}
			else
			{
				BlockTexture blockTexture = _texturePack.GetBlockTexture( block );
				if( blockTexture != null )
				{
					Texture texture = blockTexture.Textures[BlockTextureFlags.Top];
					var pixels =
						texture
							.Image
							.Pixels( texture.GetRectangleForImageIndex( 0 ) )
							.Select( c => new { r = (float)c.R, g = (float)c.R, b = (float)c.B, a = (float)c.A / 255f } )
							.Where( c => c.a > 0 )
							.Buffer();

					var t =
						pixels.Aggregate(
							( c1, c2 ) => new { r = c1.r + c2.r, g = c1.g + c2.g, b = c1.b + c2.b, a = 1f } );

					int count = pixels.Count();
					Color avg = Color.FromArgb(
						255,
						(int)( t.r / count ),
						(int)( t.g / count ),
						(int)( t.b / count )
					);

					_blockColorMapping.Add( block, avg );
				}
			}
		}

		[ContractInvariantMethod]
		private void InvariantMethod()
		{
			Contract.Invariant( _texturePack != null );
		}

		/// <summary>
		/// Create an image of a chunk where every block is a single pixel
		/// </summary>
		/// <param name="chunk">The chunk to create a pixel for</param>
		/// <returns></returns>
		public Bitmap CreatePixelChunkImage( Chunk chunk )
		{
			Contract.Requires<ArgumentNullException>( chunk != null );
			Contract.Ensures( Contract.Result<Image>() != null );

			// a chunk is 16x16 blocks, so image is 16x16pixels
			return RenderBitmap( 16, 16,
				( bm, gfx ) =>
				{
					for( byte x = 0; x < 16; x++ )
					{
						for( byte z = 0; z < 16; z++ )
						{
							// get the top block
							WorldBlock worldBlock = chunk.GetTopBlock( x, z );

							// determine pixel color
							Color color = Color.Black; // default to black when do do not have any color data
							if( worldBlock != null )
							{
								if( !_blockColorMapping.TryGetValue( worldBlock.Block, out color ) )
								{
									
								}
							}
							// paint the pixel
							bm.SetPixel( x, z, color );
						}
					}
				}
			);
		}

		public Bitmap CreateChunkImage( Chunk chunk )
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Render a new bitmap of given dimensions, calling the render action to 'fill' the bitmap
		/// </summary>
		/// <param name="width">The width of the new bitmap.</param>
		/// <param name="height">The height of the new bitmap.</param>
		/// <param name="render">The render callback</param>
		/// <returns>An <see cref="Image"/> with the final image.</returns>
		private Bitmap RenderBitmap( int width, int height, Action<Bitmap, Graphics> render )
		{
			Contract.Requires<ArgumentOutOfRangeException>( width > 0 );
			Contract.Requires<ArgumentOutOfRangeException>( height > 0 );
			Contract.Requires<ArgumentNullException>( render != null );
			Contract.Ensures( Contract.Result<Image>() != null );

			// create a drawing
			var bitmap = new Bitmap( width, height, PixelFormat.Format32bppArgb );
			using( Graphics graphics = Graphics.FromImage( bitmap ) )
			{
				// call renderer
				render( bitmap, graphics );
			}

			return bitmap;
		}
	}
}
